#include "socket.h"

#if defined(AE_WIN32)
struct tcp_keepalive {
	ULONG onoff;
	ULONG keepalivetime;
	ULONG keepaliveinterval;
};
#define SIO_KEEPALIVE_VALS _WSAIOW(IOC_VENDOR, 4)
#endif

int ae_stream_address(ae_object_t sock, ae_address_t* peer, ae_address_t* local)
{
	socklen_t namelen;
	socket_addr* rpeer = (socket_addr*)peer;
	socket_addr* rlocal = (socket_addr*)local;
	socket_engine* rsock = (socket_engine*)sock;

	if(!CHECK_OBJECT(rsock, OBJ_SOCKET) || rsock->type !=SOCK_STREAM || (rpeer == NULL && rlocal == NULL))
		return -1;

	if(rpeer) {
		namelen = sizeof(socket_addr);
		if(getpeername(rsock->sock, &rpeer->sa, &namelen) != 0)
			return -1;
	}
	if(rlocal) {
		namelen = sizeof(socket_addr);
		if(getsockname(rsock->sock, &rlocal->sa, &namelen) != 0)
			return -1;
	}
	return 0;
}

int ae_dgram_multicast(ae_object_t sock, const ae_address_t* addr, int join_or_leave)
{
	socket_engine* rsock = (socket_engine*)sock;
	const socket_addr* raddr = (const socket_addr*)addr;

	if(!CHECK_OBJECT(rsock, OBJ_SOCKET) || raddr == NULL || rsock->type !=SOCK_DGRAM || rsock->family != raddr->sa.sa_family)
		return -1;

	if(rsock->family == AF_INET) {
		int op;
		struct ip_mreq mreq;

		if(join_or_leave != 0)
			op = IP_ADD_MEMBERSHIP;
		else
			op = IP_DROP_MEMBERSHIP;
		memcpy(&mreq.imr_multiaddr, &raddr->ipv4.sin_addr, sizeof(mreq.imr_multiaddr));
		mreq.imr_interface.s_addr = htonl(INADDR_ANY);
		if(setsockopt(rsock->sock, IPPROTO_IP, op, (char *)&mreq, sizeof(mreq)) == 0)
			return 0;
	}
	else if(rsock->family == AF_INET6) {
		int op;
		struct ipv6_mreq mreq;

		if(join_or_leave != 0)
			op = IPV6_JOIN_GROUP;
		else
			op = IPV6_LEAVE_GROUP;
		memcpy(&mreq.ipv6mr_multiaddr, &raddr->ipv6.sin6_addr, sizeof(mreq.ipv6mr_multiaddr));
		mreq.ipv6mr_interface = 0;
		if(setsockopt(rsock->sock, IPPROTO_IPV6, op, (char *)&mreq, sizeof(mreq)) == 0)
			return 0;
	}
	return -1;
}

int ae_dgram_multicast_ttl(ae_object_t sock, int ttl)
{
	socket_engine* rsock = (socket_engine*)sock;

	if(!CHECK_OBJECT(rsock, OBJ_SOCKET) || rsock->type !=SOCK_DGRAM)
		return -1;

	if(rsock->family == AF_INET) {
		if(setsockopt(rsock->sock, IPPROTO_IP, IP_MULTICAST_TTL, (char*)&ttl, sizeof(ttl)) == 0)
			return 0;
	}
	else if(rsock->family == AF_INET6) {
		if(setsockopt(rsock->sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (char*)&ttl, sizeof(ttl)) == 0)
			return 0;
	}
	return -1;
}

int ae_dgram_multicast_loop(ae_object_t sock, int loop)
{
	socket_engine* rsock = (socket_engine*)sock;

	if(!CHECK_OBJECT(rsock, OBJ_SOCKET) || rsock->type !=SOCK_DGRAM)
		return -1;

	if(rsock->family == AF_INET) {
		if(setsockopt(rsock->sock, IPPROTO_IP, IP_MULTICAST_TTL, (char*)&loop, sizeof(loop)) == 0)
			return 0;
	}
	else if(rsock->family == AF_INET6) {
		if(setsockopt(rsock->sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (char*)&loop, sizeof(loop)) == 0)
			return 0;
	}
	return -1;
}

int ae_socket_sync_timeout(ae_object_t sock, int seconds, int type)
{
	int op;
#if defined(AE_WIN32)
	DWORD timeout;
#else
	struct timeval timeout;
#endif
	socket_engine* rsock = (socket_engine*)sock;

	if(!CHECK_OBJECT(rsock, OBJ_SOCKET) || seconds < 1)
		return -1;

#if defined(AE_WIN32)
	timeout = seconds * 1000;
#else
	timeout.tv_sec = seconds;
	timeout.tv_usec = 0;
#endif

	if(type == AE_TYPE_IN)
		op = SO_RCVTIMEO;
	else
		op = SO_SNDTIMEO;
	return setsockopt(rsock->sock, SOL_SOCKET, op, (char*)&timeout, sizeof(timeout));
}

int ae_stream_nodelay(ae_object_t sock, int nodelay)
{
	socket_engine* rsock = (socket_engine*)sock;

	if(!CHECK_OBJECT(rsock, OBJ_SOCKET) || rsock->type !=SOCK_STREAM)
		return -1;

	return setsockopt(rsock->sock, IPPROTO_TCP, TCP_NODELAY, (char*)&nodelay, sizeof(nodelay));
}


int ae_stream_keeplive(ae_object_t sock, int onoff, int idle, int interval)
{
	socket_engine* rsock = (socket_engine*)sock;
#if defined(AE_WIN32)
	DWORD bytes;
	struct tcp_keepalive tk;
#endif

	if(!CHECK_OBJECT(rsock, OBJ_SOCKET) || idle < 0 || interval < 0 || rsock->type !=SOCK_STREAM)
		return -1;

#if defined(AE_WIN32)
	tk.onoff = onoff;
	tk.keepalivetime = idle * 1000;
	tk.keepaliveinterval = interval * 1000;
	return WSAIoctl(rsock->sock, SIO_KEEPALIVE_VALS, &tk, sizeof(tk), NULL, 0, &bytes, NULL, NULL);
#else
	setsockopt(rsock->sock, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle));
	setsockopt(rsock->sock, IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(interval));
	return setsockopt(rsock->sock, SOL_SOCKET, SO_KEEPALIVE, &onoff, sizeof(onoff));
#endif
}
